package logutils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import se.sics.tac.aw.InsertionSort;




public class EntertainmentFromLog {

	String agentId;
	String logName;
	//
	// Read the log and set these arrays from the log
	//
	private ArrayList<String> log;
	private HashMap<String, String> auctionMap;
	private HashMap<String, String> reverseAuctionMap = new HashMap<String, String>();
	private int[][] ticketsInitial;
	private int[][] entCostsPrefs;
	private HashMap<Integer, String> entCostsPrefsAsMap;
	private int[][] clientStayPrefs; // May replace this with following two
	private int[] clientStartDay = new int[8];
	private int[] clientEndDay = new int[8];
	private int[][] clientActualStays;
	private int[] clientActualStartDay = new int[8];
	private int[] clientActualEndDay = new int[8];
	private int[][] finalTicketAllocation; // [Client][ent] = day
	private int[][] finalTicketAllocationDayEnt; // [Day][ent] = number of
	// tickets
	private int[][] finalTicketAllocationClientDay; // [Client][Day] = ent
	private int[] sortedTicketPrices;

	//
	// vary during course of run
	//
	private int[][] unAllocatedTickets = new int[5][3];// By [day][ent]
	private int[][] allocatedTickets = new int[8][5];// By [client][day]
	int profit;
	String bestAuction; // most profitable auction to bid in
	int topPrice; // Don't go higher than this
	int minAllocPrice; // Lowest price allocated ticket
	int minAllocDay; // Day on which lowest rice ticket is allocated
	int minAllocEnt; // Ent which lowest rice ticket is allocated

	EntertainmentFromLog(String l, String agentId) {
		// The log file as an ArrayList & the number of the agent to watch.
		this.logName = l;
		this.agentId = agentId;
		// read all the data we need from the log
		setDataArrays();
	}

	private void setDataArrays() {
		// reads all the data that is needed from the log
		ParseLog p = new ParseLog();

		// Read the log data into an ArrayList
		this.log = p.readLog(logName);

		// Set two maps. 1) Auction->Day:Ent. 2) Day:Ent->Auction
		setAuctionMaps(p);

		// Sets the initial ticket allocation ticketsInitial[day][ent]
		setTicketsInitial(p);

		// Get the clients preferred ticket prices
		// 1) as an int array entCostsPrefs[client][ent]
		// 2) as map, key = cost, value=client:ent
		setEntCostsPrefs(p);

		// Get the length of time that the client would like to stay
		// As int array [client][day]. Values are 1 of client would like to stay
		// 0 if not.
		setClientStayPrefs(p);

		// Get the length of time that the client is staying at the end of the
		// run
		// As int array [client][day]. Values are 1 of client is staying
		// 0 if not.
		setActualClientStays(p);

		// Get the final ticket allocation from the log
		setFinalTicketAllocations(p);

		// End of basic set up from log. One or two other things to set.
		// Sort the prices that the clients are prepared to pay into a list in
		// ascending order
		this.sortedTicketPrices = setSortedTicketPrices(this.entCostsPrefsAsMap);
	}

	private void setAuctionMaps(ParseLog p) {
		this.auctionMap = p.getAuctions(this.log);

		String auction;
		String dayEnt;

		Iterator<String> it = this.auctionMap.keySet().iterator();
		while (it.hasNext()) {
			auction = it.next();
			dayEnt = auctionMap.get(auction);
			this.reverseAuctionMap.put(dayEnt, auction);
		}
	}

	public HashMap<String, String> getAuctionMap() {
		return this.auctionMap;
	}

	public HashMap<String, String> getReverseAuctionMap() {
		return this.auctionMap;
	}

	private void setTicketsInitial(ParseLog p) {
		this.ticketsInitial = p.getInitialTicketAllocations(this.log,
				this.agentId, this.auctionMap);
	}

	public int[][] getTicketsInitial() {
		return this.ticketsInitial;
	}

	private void setEntCostsPrefs(ParseLog p) {
		this.entCostsPrefs = p.getEntCostsPrefs(this.log, this.agentId);
		this.entCostsPrefsAsMap = new HashMap<Integer, String>();
		String a;
		String tmp;
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 3; j++) {
				a = Integer.toString(i) + ":" + Integer.toString(j);
				if (this.entCostsPrefsAsMap
						.containsKey(this.entCostsPrefs[i][j])) {
					// deal with things elements having the same numerical value
					tmp = this.entCostsPrefsAsMap.get(this.entCostsPrefs[i][j]);
					a = a + ":" + tmp;
					this.entCostsPrefsAsMap.put(this.entCostsPrefs[i][j], a);
				} else {
					this.entCostsPrefsAsMap.put(this.entCostsPrefs[i][j], a);
				}
			}
		}
	}

	public int[][] getEntCostsPrefs() {
		return this.entCostsPrefs;
	}

	public HashMap<Integer, String> getEntCostsPrefsAsMap() {
		return this.entCostsPrefsAsMap;
	}

	private void setClientStayPrefs(ParseLog p) {
		int cp;
		int jm = 0;
		this.clientStayPrefs = p.getClientPrefs(this.log, this.agentId);
		for (int i = 0; i < 8; i++) {
			this.clientStartDay[i] = -1;
			this.clientEndDay[i] = -1;
			if (this.clientStayPrefs[i][0] == 1){
				this.clientStartDay[i] = 0;
			}
			jm =0;
			for (int j = 1; j < 5; j++) {
				cp = this.clientStayPrefs[i][j] - this.clientStayPrefs[i][jm];
				if (cp == 1) {
					this.clientStartDay[i] = j;
				} else if (cp == -1) {
					this.clientEndDay[i] = jm;
				}
				jm = j;
			}
		}
	}

	public int[][] getClientPrefs() {
		return this.clientStayPrefs;
	}
	public int[] getClientStartDay(){
		return this.clientStartDay;
	}
	public int[] getClientEndDay(){
		return this.clientEndDay;
	}

	private void setActualClientStays(ParseLog p) {
		int cp;
		int jm = 0;
		this.clientActualStays = p.getActualClientStays(this.log, this.agentId);
		for (int i = 0; i < 8; i++) {
			this.clientActualStartDay[i] = -1;
			this.clientActualEndDay[i] = -1;
			if (this.clientActualStays[i][0] == 1){
				this.clientActualStartDay[i] = 0;
			}
			jm = 0;
			for (int j = 0; j < 5; j++) {
				cp = this.clientActualStays[i][j] - this.clientActualStays[i][jm];
				if (cp == 1) {
					this.clientActualStartDay[i] = j;
				} else if (cp == -1) {
					this.clientActualEndDay[i] = jm;
				}
				jm = j;
			}
		}
		
	}

	public int[][] getClientStays() {
		return this.clientActualStays;
	}
	public int[] getClientActualStartDay(){
		return this.clientActualStartDay;
	}
	public int[] getClientActualEndDay(){
		return this.clientActualEndDay;
	}

	private void setFinalTicketAllocations(ParseLog p) {

		// these are just different representations of the way that
		// the agent has allocated the tickets
		this.finalTicketAllocation = p.getFinalEntAlloc(this.log, this.agentId);
		// need to subtract one from days
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 3; j++) {
				this.finalTicketAllocation[i][j]--;
			}
		}
		// Need this to compare with ticketsInitial which is also by [day][ent]
		this.finalTicketAllocationDayEnt = new int[5][3];
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 3; j++) {
				if (this.finalTicketAllocation[i][j] >= 0) {
					this.finalTicketAllocationDayEnt[finalTicketAllocation[i][j]][j]++;
				}
			}
		}
		// Need this to compare with my calculated allocation in format
		// [cli][day]=ent.
		this.finalTicketAllocationClientDay = new int[8][5];
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 5; j++) {
				this.finalTicketAllocationClientDay[i][j] = -1;
			}
		}
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 3; j++) {
				if (this.finalTicketAllocation[i][j] >= 0) {
					this.finalTicketAllocationClientDay[i][finalTicketAllocation[i][j]] = j;
				}
			}
		}
	}

	public int[][] getFinalTicketAllocation() {
		return this.finalTicketAllocation;
	}

	public int[][] getFinalTicketAllocationDayEnt() {
		return this.finalTicketAllocationDayEnt;
	}

	public int[][] getFinalTicketAllocationClientDay() {
		return this.finalTicketAllocationClientDay;
	}

	private int[] setSortedTicketPrices(HashMap<Integer, String> eCPM) {
		ArrayList<Integer> a = new ArrayList<Integer>();
		Iterator<Integer> it = eCPM.keySet().iterator();
		int m;
		while (it.hasNext()) {
			m = it.next();
			a.add(m);
		}
		int k = a.size();
		int[] b = new int[k];

		Iterator<Integer> ai = a.iterator();
		int i = 0;
		while (ai.hasNext()) {
			b[i] = ai.next();
			i++;
		}

		b = InsertionSort.insertionSort(b, k);
		return b;
	}

	public int[] getSortedTicketPrices() {
		return this.sortedTicketPrices;
	}

	public int getProfitMadeByAgent() {
		int sum = 0;
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 3; j++) {
				if (this.finalTicketAllocation[i][j] >= 0) {
					sum = sum + this.entCostsPrefs[i][j];
				}
			}
		}
		return sum;
	}

	/*
	 * That ends setting up data from log. Next functions are all calculating
	 * data.
	 */

	public void setOptimumTicketAllocation(int[][] ticketsDayEnt, int[] clientStartDay, int[] clientEndDay) {

		String[] tmp;
		int client;
		int ent;
		boolean[][] hasEntAlready = new boolean[8][5];
		boolean alloc = false;
		boolean gotnext = false;

		this.profit = 0;

		int nPrice = this.sortedTicketPrices.length;

		for (int i = 0; i < 5; i++) {
			for (int j = 0; j < 3; j++) {
				this.unAllocatedTickets[i][j] = ticketsDayEnt[i][j];
			}
		}
		// initialise to -1
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 5; j++) {
				this.allocatedTickets[i][j] = -1;
			}
		}
		// Initialise tickets to buy to the original sorted price list

		// Step backwards through the ticket prices (highest first)

		for (int k = nPrice - 1; k > -1; k--) {
			tmp = this.entCostsPrefsAsMap.get(this.sortedTicketPrices[k])
					.split(":");

			for (int j = 0; j < tmp.length; j = j + 2) {
				client = Integer.parseInt(tmp[j]);
				ent = Integer.parseInt(tmp[j + 1]);
				alloc = false;

				// for (int day = 0; day < 5; day++) {
				int first = clientStartDay[client];
				int last = clientEndDay[client] + 1;
				if (first == -1){
					// failed to find a stay at all;
					//System.out.println("No stay set - don't try to allocate tickets");
					break;
				}
				for (int day = first; day < last; day++) {

					// Ugly, could be done better
					if (this.unAllocatedTickets[day][ent] != 0
					// && !hasEntAlready[client][ent] // Not sure if this is
							// necessary or not.
							// && clientStayPrefs[client][day] != 0
							&& allocatedTickets[client][day] == -1) {
						//System.out.println("DB DAY = " + day + " client = "
							//	+ client + " ent = " + ent);

						this.allocatedTickets[client][day] = ent;
						this.unAllocatedTickets[day][ent]--;
						hasEntAlready[client][ent] = true;
						this.profit = this.profit + this.sortedTicketPrices[k];
						alloc = true;
						this.minAllocPrice = this.sortedTicketPrices[k];
						this.minAllocDay = day;
						this.minAllocEnt = ent;
						break;
					}
				}
				if (!alloc && !gotnext) {
					// Pick up the first unallocated ticket and work out how to
					// bid for it
					for (int day = 0; day < 5; day++) {
						if (this.clientStayPrefs[client][day] == 1
								&& hasEntAlready[client][ent] == false) {
							this.bestAuction = reverseAuctionMap.get(Integer
									.toString(day)
									+ ":" + Integer.toString(ent));
							this.topPrice = this.sortedTicketPrices[k];
							gotnext = true;
							break;
						}
					}
				}
			}
		}
	}

	public int getProfit() {
		return this.profit;
	}

	public String getNextAuctionToBuyIn() {
		return this.bestAuction;
	}

	public int getMaxBuyPrice() {
		return this.topPrice;
	}

	public int[][] getAllocatedTickets() {
		return this.allocatedTickets;
	}

	public int[][] getUnAllocatedTickets() {
		return this.unAllocatedTickets;
	}

	public String getNextAuctionToSellIn() {
		String auction = null;
		boolean isset = false;
		for (int i = 0; i < 5; i++) {
			for (int j = 0; j < 3; j++) {
				if (this.unAllocatedTickets[i][j] != 0) {
					auction = reverseAuctionMap.get(Integer.toString(i) + ":"
							+ Integer.toString(j));
					isset = true;
					break;
				}
			}
			if (isset) {
				break;
			}
		}
		if (isset) {
			this.minAllocPrice = 0;
			return auction;
		} else {
			// No unallocated ticket so just get least worthwhile ticket
			auction = reverseAuctionMap.get(Integer.toString(minAllocDay) + ":"
					+ Integer.toString(minAllocEnt));
			return auction;
		}
	}

	public int getMinSellPrice() {
		return this.minAllocPrice;
	}

	public static void main(String args[]) {
		HashMap<String, String> auctionMap;
		HashMap<String, String> reverseAuctionMap;
		int[][] initialTickets;
		int[][] entCostsPrefs;
		HashMap<Integer, String> entCostsPrefsAsMap;
		int[][] clientPrefs;
		int[] clientEndDay;
		int[]clientStartDay;
		int[][] clientStays;
		int[] clientActEndDay;
		int[] clientActStartDay;
		int[][] finalTA;
		int[][] finalTADE;
		int[][] finalTACD;
		int[] sortedP;

		int[][] ticketAllocation;
		int[][] ticketsNotAllocated;
		String auctionToBidIn;
		int topBid;
		int profit;
		int aprofit;
		String nextAuctionToSellIn;
		int minPrice;
		int delta = 0;
		int avgDelta = 0;

		// Test driver routine for ents class
		ArrayList<String> LogString = new ArrayList<String>();
		String agentList[];
		String l = args[0];
		String agentId;
		ParseLog p = new ParseLog();
		LogString = p.readLog(l);
		agentList = p.getAgentList(LogString);

		/*for (int i = 0; i < 8; i++) {
			System.out.println("Agent ID = " + agentList[i]);
		}*/
		//this is a test
		for (int i = 0; i < 8; i++) {
			agentId = agentList[i];

			boolean debug = true;
			// test that data is correctly read from the log

			EntertainmentFromLog e = new EntertainmentFromLog(l, agentId);
			ArrayList<String> dumpToFile = new ArrayList<String>();

			auctionMap = e.getAuctionMap();
			reverseAuctionMap = e.getReverseAuctionMap();
			initialTickets = e.getTicketsInitial();
			entCostsPrefs = e.getEntCostsPrefs();
			entCostsPrefsAsMap = e.getEntCostsPrefsAsMap();
			clientPrefs = e.getClientPrefs();
			clientEndDay = e.getClientEndDay();
			clientStartDay = e.getClientStartDay();
			clientStays = e.getClientStays();
			clientActEndDay = e.getClientActualEndDay();
			clientActStartDay = e.getClientActualStartDay();
			finalTA = e.getFinalTicketAllocation();
			finalTADE = e.getFinalTicketAllocationDayEnt();
			finalTACD = e.getFinalTicketAllocationClientDay();
			sortedP = e.getSortedTicketPrices();
			aprofit = e.getProfitMadeByAgent();

			dumpToFile = OutputWriter.comment("Log name: " + l + " Agent ID: "
					+ agentId, dumpToFile);
			System.out.println("Agent ID :" + agentId);
			if (debug) {
				dumpToFile = OutputWriter.comment("Data based on log analysis",
						dumpToFile);
				dumpToFile = OutputWriter.outMapStrStr(auctionMap, "Auction",
						"Day:Ent", "AuctionMap", dumpToFile);
				dumpToFile = OutputWriter.outMapStrStr(reverseAuctionMap,
						"Day:Ent", "Auction", "Reverse AuctionMap", dumpToFile);
				dumpToFile = OutputWriter.outIntArray(initialTickets, 5, 3,
						"Tickets allocated by agent", dumpToFile);
				dumpToFile = OutputWriter.outIntArray(entCostsPrefs, 8, 3,
						"Client's preferred cost for entartainments",
						dumpToFile);
				dumpToFile = OutputWriter.outMapIntStr(entCostsPrefsAsMap,
						"Price", "Client:Ent",
						"Client's preferred costs as map", dumpToFile);
				dumpToFile = OutputWriter.outIntArray(clientPrefs, 8, 5,
						"Client's prefered stays", dumpToFile);
				dumpToFile = OutputWriter.outIntArray(clientStays, 8, 5,
						"Client's actual stays from end of log", dumpToFile);
				dumpToFile = OutputWriter
						.outIntArray(
								finalTA,
								8,
								3,
								"Final ticket allocation by agent (client,ent) = day, from end of log",
								dumpToFile);
				dumpToFile = OutputWriter
						.outIntArray(
								finalTADE,
								5,
								3,
								"Final ticket allocation by agent (day,ent) = number, from end of log",
								dumpToFile);
				dumpToFile = OutputWriter
						.outIntArray(
								finalTACD,
								8,
								5,
								"Final ticket allocation by agent (client,day) = ent, from end of log",
								dumpToFile);
				dumpToFile = OutputWriter.outIntArrayD1(sortedP,
						"Tickets prices in descending order", dumpToFile);
				dumpToFile = OutputWriter.stringDump(
						"Actual profit made by agent on entertainment", Integer
								.toString(aprofit), dumpToFile);
			}

			// Now try and do something with the data
			// First calculate the best allocation of tickets with the starting
			// data

			e.setOptimumTicketAllocation(initialTickets, clientStartDay, clientEndDay);
			ticketAllocation = e.getAllocatedTickets();
			ticketsNotAllocated = e.getUnAllocatedTickets();
			auctionToBidIn = e.getNextAuctionToBuyIn();
			topBid = e.getMaxBuyPrice();
			profit = e.getProfit();
			nextAuctionToSellIn = e.getNextAuctionToSellIn();
			minPrice = e.getMinSellPrice();

			if (debug) {
				dumpToFile = OutputWriter
						.comment(
								"Data based on optimising the initial alloaction of tickes",
								dumpToFile);
				dumpToFile = OutputWriter.outIntArray(ticketAllocation, 8, 5,
						"Optimised allocation of initial tickets", dumpToFile);
				dumpToFile = OutputWriter.outIntArray(ticketsNotAllocated, 5,
						3, "Tickets which could not be allocated", dumpToFile);
				dumpToFile = OutputWriter.stringDump("Next auction to buy in",
						auctionToBidIn, dumpToFile);
				dumpToFile = OutputWriter.stringDump("Max price to pay",
						Integer.toString(topBid), dumpToFile);
				dumpToFile = OutputWriter.stringDump("Next auction to sell in",
						nextAuctionToSellIn, dumpToFile);
				dumpToFile = OutputWriter.stringDump("Min price to get",
						Integer.toString(minPrice), dumpToFile);
				dumpToFile = OutputWriter.stringDump(
						"Expected profit on entertainment", Integer
								.toString(profit), dumpToFile);
			}

			// Recalculate using with the data from the end of the run
			// 

			e.setOptimumTicketAllocation(finalTADE, clientActStartDay, clientActEndDay);
			ticketAllocation = e.getAllocatedTickets();
			ticketsNotAllocated = e.getUnAllocatedTickets();
			topBid = e.getMaxBuyPrice();
			profit = e.getProfit();
			nextAuctionToSellIn = e.getNextAuctionToSellIn();
			minPrice = e.getMinSellPrice();

			profit = e.getProfit();

			if (debug) {
				dumpToFile = OutputWriter
						.comment(
								"Data based on optimising the final alloaction of tickes",
								dumpToFile);
				 dumpToFile = OutputWriter.outIntArray(ticketAllocation, 8, 5,
				 "Optimised allocation of final tickets", dumpToFile);
				dumpToFile = OutputWriter.outIntArray(ticketsNotAllocated, 5,
						3, "Tickets which were not be allocated at end",
						dumpToFile);
				dumpToFile = OutputWriter.stringDump(
						"Next auction to buy in (if there was one)",
						auctionToBidIn, dumpToFile);
				dumpToFile = OutputWriter.stringDump("Max price to pay",
						Integer.toString(topBid), dumpToFile);
				dumpToFile = OutputWriter.stringDump(
						"Next auction to sell in (if there was one)",
						nextAuctionToSellIn, dumpToFile);
				dumpToFile = OutputWriter.stringDump("Min price to get",
						Integer.toString(minPrice), dumpToFile);
				dumpToFile = OutputWriter
						.stringDump(
								"Max profit on entertainment based on optimised final allocation",
								Integer.toString(profit), dumpToFile);
			}
			
			if (profit != aprofit) {
				delta = aprofit - profit;
				if (delta < 0) {
					System.out
					.println("\nWARNING: Allocator gives different result from scorer. Scorer: "
							+ aprofit + ", allocator: " + profit + " Delta: " + delta);
					System.out.println ("Negative difference ");
					System.exit(1);
				}
				System.out
						.println("\nWARNING: Allocator gives different result from scorer. Scorer: "
								+ aprofit + ", allocator: " + profit + " Delta: " + delta);
				avgDelta = avgDelta + delta;
			} else {
				System.out.println("\nScorer profit: " + aprofit
						+ ", allocator profit: " + profit);
			}

			FileHandler fh = new FileHandler();
			File f = new File(args[0] + "_processed_toCheck_client_" + i);
			try {
				fh.write(f, dumpToFile);
			} catch (Exception ioe) {
				System.out.println("Caught this " + ioe);
			}

		}
		avgDelta = avgDelta / 8;
		System.out.println("Average difference = " + avgDelta);
	}

}
